﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using IBS.ServiceContracts;
using IBS.Model;
using IBS.Bll.Tools;
using System.Transactions;

namespace IBS.Bll.Cards
{
    public class CardService : ServiceBase, ICardService
    {
        private readonly ISecurityService _securityService;
        private readonly IAccountService _accountService;
        private readonly ICurrencyService _currencyService;
        private readonly ITransferService _transferService;

        #region Constructors

        public CardService(IIbsContext context,
            ISecurityService securityService,
            IAccountService accountService,
            ICurrencyService currencyService,
            ITransferService transferService)
            : base(context)
        {

            _securityService = securityService;
            _accountService = accountService;
            _currencyService = currencyService;
            _transferService = transferService;
        }

        #endregion

        #region Maintenance

        public IList<Card> GetClientsCard(Client client, Func<Card, bool> criteria = null, int? page = null)
        {
            IList<Card> result;
            if (criteria == null)
                criteria = c => true;
            result = _context.Cards
                .GetByCriteria(page,  c => !c.isDeleted && c.Client.Id == client.Id && criteria(c))
                .ToList();
            return result;
        }

        public IList<Card> GetCards(Func<Card, bool> criteria = null, int? page = null)
        {
            if (criteria == null)
                criteria = c => true;
            return _context.Cards.GetByCriteria(page, c=> !c.isDeleted && criteria(c));
        }

        public Card GetCardByNumber(string cardNumber)
        {
            try
            {
                return _context.Cards.Single(c => c.Number == cardNumber && !c.isDeleted);
            }
            catch (InvalidOperationException)
            {
                throw new BusinessException("Card does not exist");
            }
        }

        public void LockCard(string cardNumber)
        {
            var card = GetCardByNumber(cardNumber);
            if (card.IsBlocked)
                throw new BusinessException("Card already locked");
            card.IsBlocked = true;
            _context.SaveChanges();
        }

        public void UnlockCard(string cardNumber)
        {
            var card = GetCardByNumber(cardNumber);
            if (!card.IsBlocked)
                throw new BusinessException("Card already unlocked");
            card.IsBlocked = false;
            _context.SaveChanges();
        }

        #endregion

        #region Transfer

        public Transfer TransferToCard(string fromCardNumber, string toCardNumber, decimal amount, string ISO, string userName)
        {

            //Validation
            var fromCard = GetCardByNumber(fromCardNumber);
            var toCard = GetCardByNumber(toCardNumber);

            if (!_securityService.IsCardOwner(fromCard, userName))
                throw new BusinessException("You can't do this operation");

            if (fromCard.IsBlocked || fromCard.Account.IsBlocked)
                throw new BusinessException("Your card or account is blocked. Unlock it and try again");

            if (toCard.IsBlocked || toCard.IsBlocked)
                throw new BusinessException("Target card is blocked");

            return _transferService.Transfer(fromCard, toCard, amount, ISO, userName);
        }

        public Transfer TransferToAccount(string fromCardNumber, string toAccountNumber, decimal amount, string ISO, string userName)
        {
            //Validation
            Card fromCard = GetCardByNumber(fromCardNumber);
            Account toAccount = _accountService.GetAccountByNumber(toAccountNumber);

            if (!_securityService.IsCardOwner(fromCard, userName))
                throw new BusinessException("You can't do this operation");

            if (fromCard.IsBlocked || fromCard.Account.IsBlocked)
                throw new BusinessException("Your card or account is blocked. Unlock it and try again");

            if (toAccount.IsBlocked)
                throw new BusinessException("Target account is blocked");

            return _transferService.Transfer(fromCard, toAccount, amount, ISO, userName);
        }

        public Transfer Deposit(string cardNumber, decimal amount)
        {
            var card = GetCardByNumber(cardNumber);

            if (card.IsBlocked || card.Account.IsBlocked)
                throw new BusinessException("Your card or account is blocked. Unlock it and try again");

            return _transferService.Deposit(card, amount);
        }

        public Transfer Withdraw(string cardNumber, decimal amount)
        {
            var card = GetCardByNumber(cardNumber);
            if (card.IsBlocked || card.Account.IsBlocked)
                throw new BusinessException("Your card or account is blocked. Unlock it and try again");

            return _transferService.Withdraw(card, amount);
        }

        public Card CreateNewCard(Client client, CardType cardType, string login, decimal startAmont = 0)
        {
            string cardAccountISO = null;
            switch (cardType)
            {
                case CardType.VisaClassic:
                    cardAccountISO = "USD";
                    break;
                case CardType.MasterCard:
                    cardAccountISO = "BYR";
                    break;
                default:
                    throw new ArgumentOutOfRangeException("cardType");
            }
            var cardAccount = _accountService.CreateAccount(client, cardAccountISO, login);

            //Create card and match it to account 
            var card = CreateCardForAccount(cardAccount.Number, cardType, login);
            card.Number = NumbersGenerator.GenerateCardNumber(cardType);
            _context.SaveChanges();
            if (startAmont > 0)
                _transferService.Deposit(card, startAmont);
            return card;
        }

        public void DeleteCard(string cardNumber)
        {
            var card = GetCardByNumber(cardNumber);
            card.isDeleted = true;
            _context.SaveChanges();
        }

        private Card CreateCardForAccount(string accountNumber, CardType cardType, string login)
        {
            var account = _accountService.GetAccountByNumber(accountNumber);
            var card = new Card();
            card.Account = account;
            card.Client = account.Client;
            card.CreatedDate = DateTime.Now;
            card.Type = (int)cardType;
            card.UpdateLogin = login;
            _context.Cards.AddObject(card);
            return card;
        }

        public Card CreateAdditionalCard(string existingCardNumber, string login)
        {
            var existingCard = GetCardByNumber(existingCardNumber);

            var account = existingCard.Account;
            if (account.Card.Count >= 3)
                throw new BusinessException("You can't create cards more than 3 for one account");

            var additionalCard = CreateCardForAccount(account.Number, (CardType)existingCard.Type, login);
            return additionalCard;
        }

        #endregion


    }
}
